package excel

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"reflect"
	"time"

	"gitee.com/yookie316/goutils/timex"
	"gitee.com/yookie316/goutils/toolx"
	"github.com/mitchellh/mapstructure"
	"github.com/xuri/excelize/v2"
)

const (
	defaultSheetName = "Sheet1"
)

type Excel struct {
	sheetName    string
	skipFirstRow bool
}

func NewExcel(opts ...Option) *Excel {
	excel := &Excel{
		sheetName:    defaultSheetName,
		skipFirstRow: true,
	}
	for _, o := range opts {
		o(excel)
	}
	return excel
}

func (e *Excel) Excel2Map(fn io.Reader, model interface{}) ([]map[string]interface{}, error) {
	fs, _, err := getStructFields(model)
	if err != nil {
		return nil, err
	}

	f, err := excelize.OpenReader(fn)
	if err != nil {
		return nil, err
	}
	//    excelize.CoordinatesToCellName(1, 1) // returns "A1", nil
	//excelize.CoordinatesToCellName(1,1)

	rows, err := f.GetRows(e.sheetName)
	if e.skipFirstRow {
		rows = rows[1:]
	}
	if err != nil {
		return nil, err
	}
	return slice2Map(rows, fs)
}

func slice2Map(rows [][]string, columns []string) ([]map[string]interface{}, error) {
	var result []map[string]interface{}
	l := len(rows)
	for i := 0; i < l; i++ {
		tmp := make(map[string]interface{})
		for j, cell := range rows[i] {
			tmp[columns[j]] = cell
		}
		result = append(result, tmp)
	}

	return result, nil
}

func stringToTimeHookFunc(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
	if f.Kind() != reflect.String {
		return data, nil
	}
	if t != reflect.TypeOf(time.Time{}) {
		return data, nil
	}

	// return timex.ParseShortCSTInLocation(data.(string))
	tm, err := timex.ParseCSTInLocation(data.(string))

	if err == nil {
		return tm, nil
	}

	return timex.ParseShortCSTInLocation(data.(string))
}

func (e *Excel) Excel2Structs(fn io.Reader, v interface{}) error {
	mapData, err := e.Excel2Map(fn, v)
	if err != nil {
		return err
	}

	conf := mapstructure.DecoderConfig{
		WeaklyTypedInput: true,
		Result:           &v,
		DecodeHook:       stringToTimeHookFunc,
	}
	decoder, _ := mapstructure.NewDecoder(&conf)
	return decoder.Decode(mapData)
}

func (e *Excel) data2Excel(input interface{}, model interface{}) (*excelize.File, error) {
	// parse model
	fields, tableHeaders, err := getStructFields(model)
	if err != nil {
		return nil, err
	}

	// transform data to [][]interface{}
	data, err := Transform(input, fields)
	if err != nil {
		return nil, err
	}

	file := excelize.NewFile()

	streamWriter, err := file.NewStreamWriter(defaultSheetName)
	if err != nil {
		return nil, err
	}

	var header []interface{}
	for _, v := range tableHeaders {
		header = append(header, v)
	}
	if err := streamWriter.SetRow("A1", header); err != nil {
		return nil, err
	}
	// write data
	row := 2
	for _, v := range data {
		cell, _ := excelize.CoordinatesToCellName(1, row)
		err := streamWriter.SetRow(cell, v)
		if err != nil {
			fmt.Println("setRow error", err)
		}
		row++
	}

	if err := streamWriter.Flush(); err != nil {
		return nil, err
	}
	return file, nil

}

func (e *Excel) Save2Excel(data interface{}, model interface{}, dest string) error {
	if dest == "" {
		return errors.New("dest is empty")
	}
	err := toolx.CreateDir(dest)
	if err != nil {
		return err
	}
	f, err := e.data2Excel(data, model)
	if err != nil {
		return err
	}
	return f.SaveAs(dest)
}

func (e *Excel) DownloadExcel(data interface{}, model interface{}, w http.ResponseWriter) error {
	f, err := e.data2Excel(data, model)
	if err != nil {
		return err
	}
	_, err = f.WriteTo(w)
	return err
}
